// 版权2019 etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package rafttest

import (
	"fmt"
	"strconv"
	"testing"

	"github.com/cockroachdb/datadriven"
)

// 句柄是数据驱动交互测试的入口点。命令和
// 参数是从提供的测试数据中解析出来的。
// 数据解析期间的错误通过提供的*测试报告。T来自raft节点和
// 存储引擎的错误将报告给输出缓冲区。
func (env *InteractionEnv) Handle(t *testing.T, d datadriven.TestData) string {
	env.Output.Reset()
	var err error
	switch d.Cmd {
	case "_breakpoint":
		// 当需要在较长的测试文件中调查问题时，这是一个附加调试器的帮助案例。在这种情况下，在感兴趣的行为开始之前添加以下小节：
		// 
		// \u断点：
		// ----
		// ok 
		// 
		// 并在上面的“case”上设置断点。
	case "add-nodes":
		// 示例：
		// 
		// 添加节点<要添加的节点数>投票者=（1 2 3）学习者=（4 5）index=2 content=foo-
		err = env.handleAddNodes(t, d)
	case "campaign":
		// 示例：
		// 
		// 活动<id of candidate>
		err = env.handleCampaign(t, d)
	case "compact":
		// 示例：
		// 
		// compact<id><new first index>
		err = env.handleCompact(t, d)
	case "deliver-msgs":
		// 为给定收件人传递消息。
		// 
		// 示例：
		// 
		// 交付MSG<idx>
		err = env.handleDeliverMsgs(t, d)
	case "process-ready":
		// 示例：
		// 
		// 流程就绪3 
		err = env.handleProcessReady(t, d)
	case "log-level":
		// 设置日志级别。无禁用所有输出，包括来自测试
		// 线束的输出（错误除外）。
		// 
		// 示例：
		// 
		// 日志级别警告
		err = env.handleLogLevel(t, d)
	case "raft-log":
		// 打印筏日志。
		// 
		// 示例：
		// 
		// raft log 3 
		err = env.handleRaftLog(t, d)
	case "raft-state":
		// 打印所有节点的raft状态（节点是否为前导、
		// 跟随等）。节点n的信息基于
		// n的视图。
		err = env.handleRaftState()
	case "stabilize":
		// 将消息传递到ID集并在ID集上运行process ready，直到
		// 不再需要做更多的工作。如果未提供ID，则使用所有节点。
		// 
		// 示例：
		// 
		// 稳定14 
		err = env.handleStabilize(t, d)
	case "status":
		// 打印筏状态。
		// 
		// 示例：
		// 
		// 状态5 
		err = env.handleStatus(t, d)
	case "tick-heartbeat":
		// 勾选心跳间隔。
		// 
		// 示例：
		// 
		// 勾选心跳3 
		err = env.handleTickHeartbeat(t, d)
	case "transfer-leadership":
		// 转移筏头。
		// 
		// 示例：
		// 
		// 将领导权从=1转移到=4 
		err = env.handleTransferLeadership(t, d)
	case "propose":
		// 提出一个条目。
		// 
		// 示例：
		// 
		// 建议1 foo 
		err = env.handlePropose(t, d)
	case "propose-conf-change":
		// 建议配置更改，或从以前的
		// 建议的联合配置更改请求显式
		// 转换。添加节点时，此命令可用于
		// 逻辑上将节点添加到配置中，但需要
		// 添加节点才能“创建”节点。
		// 
		// propose conf change node_id[v1=<bool>][transition=<string>]
		// 命令字符串
		// 有关命令字符串格式，请参阅ConfChangesFromString。
		// 参数为：
		// node_id-建议配置更改的节点。
		// v1-一次进行一次更改，默认为false。
		// 转换-“自动”（默认）、“显式”或“隐式”。
		// 示例：
		// 
		// 建议配置更改1转换=显式
		// v1 v3 l4 r5 
		// 
		// 示例：
		// 
		// 建议配置更改2 v1=真实
		// v5 
		err = env.handleProposeConfChange(t, d)
	default:
		err = fmt.Errorf("unknown command")
	}
	if err != nil {
		env.Output.WriteString(err.Error())
	}
	// 注意：最高日志电平抑制所有输出，包括
	// 处理器。这在安装过程中非常有用，可能很健谈。
	// 但是，始终会记录错误。
	if env.Output.Len() == 0 {
		return "ok"
	}
	if env.Output.Lvl == len(lvlNames)-1 {
		if err != nil {
			return err.Error()
		}
		return "ok (quiet)"
	}
	return env.Output.String()
}

func firstAsInt(t *testing.T, d datadriven.TestData) int {
	t.Helper()
	n, err := strconv.Atoi(d.CmdArgs[0].Key)
	if err != nil {
		t.Fatal(err)
	}
	return n
}

func firstAsNodeIdx(t *testing.T, d datadriven.TestData) int {
	t.Helper()
	n := firstAsInt(t, d)
	return n - 1
}

func nodeIdxs(t *testing.T, d datadriven.TestData) []int {
	var ints []int
	for i := 0; i < len(d.CmdArgs); i++ {
		if len(d.CmdArgs[i].Vals) != 0 {
			continue
		}
		n, err := strconv.Atoi(d.CmdArgs[i].Key)
		if err != nil {
			t.Fatal(err)
		}
		ints = append(ints, n-1)
	}
	return ints
}
